package com.suredy.app.porject.tools;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.UUID;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 文件存储工具类
 * 
 * @author VIVID.G
 * @since 2015-10-26
 * @version v0.1
 */
@Component
public class FileTool {

	private final static Logger log = LoggerFactory.getLogger(FileTool.class);

	@Value("${files.container_folder}")
	private String ROOT_DIR;

	/* 默认目录 */
	private final String TMP_DIR = "tmp_dir";

	/* 缓存大小，默认4K */
	private final int BUFFER_SIZE = 1024 * 4;

	/* 断点下载头信息 */
	private final String HEADER_RANGE = "Range";

	/* 断点下载响应头信息 */
	private final String HEADER_CONTENT_RANGE = "Content-Range";

	/* 断点下载头信息，range值的前缀 */
	private final String PREF_RANGE_VAL = "bytes=";

	/**
	 * 初始化
	 */
	@PostConstruct
	protected void init() {
		if (StringUtils.isBlank(this.ROOT_DIR))
			throw new RuntimeException("Files storage DIR is empty! Please check the config file 'system.properties'");

		if (!this.ROOT_DIR.endsWith(File.separator))
			this.ROOT_DIR += File.separator;

		this.mkDirs(this.ROOT_DIR);
	}

	/**
	 * 获取文件存储的根目录
	 * 
	 * @return
	 */
	public String rootDIR() {
		return this.ROOT_DIR;
	}

	/**
	 * 存储文件到服务器本地磁盘
	 * 
	 * @param in
	 * @param key
	 * @return
	 * @throws IOException
	 */
	public File store(final InputStream in, String key) {
		if (in == null)
			return null;

		if (StringUtils.isBlank(key))
			key = this.TMP_DIR;

		String dir = this.ROOT_DIR + key + File.separator;

		if (!this.mkDirs(dir))
			throw new RuntimeException("Create folder error.");

		File file = this.newFile(dir);

		byte[] buffer = new byte[BUFFER_SIZE];
		int size = -1;
		BufferedOutputStream out = null;

		try {
			out = new BufferedOutputStream(new FileOutputStream(file));

			while ((size = in.read(buffer)) != -1) {
				out.write(buffer, 0, size);

				out.flush();
			}

			return file;
		} catch (IOException e) {
			log.error("Write to file error", e);
		} finally {
			try {
				if (out != null)
					out.close();

				if (in != null)
					in.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return null;
	}

	/**
	 * 输出文件内容（适合单线程下载）
	 * 
	 * @param out
	 * @param relativePath
	 * @return
	 * @throws IOException
	 */
	public long output(final OutputStream out, File file) {
		if (out == null)
			return -1;

		if (file == null || !file.isFile())
			return -1;

		// 空文件还下载个毛
		if (file.length() <= 0)
			return 0;

		try {
			// 下吧，英雄
			return this.output(out, file, 0, file.length() - 1);
		} catch (IOException e) {
			e.printStackTrace();
		}

		return -1;
	}

	/**
	 * 输出文件内容（断点下载）
	 * 
	 * @param out
	 * @param file
	 * @param start
	 * @param end
	 * @return
	 * @throws IOException
	 */
	public long output(final OutputStream out, File file, long fromPos, long toPos) throws IOException {
		if (out == null)
			return -1;

		if (file == null || !file.isFile())
			return -1;

		// 空文件还下载个毛
		if (file.length() <= 0) {
			out.close();
			return 0;
		}

		if (!this.validPosition(file.length(), fromPos) || !this.validPosition(file.length(), toPos))
			return -1;

		// 内容总长度
		long length = toPos - fromPos + 1;

		if (length < 0)
			return -1;

		// 一个也不读（尼玛，就是为了获取一个头信息么？）
		if (length == 0) {
			out.close();
			return 0;
		}

		RandomAccessFile raf = null;
		byte[] buffer = new byte[BUFFER_SIZE];
		int size = -1;
		long count = 0;

		try {
			// 这里不适用MappedByteBuffer的原因请查看JDK的帮助文档
			raf = new RandomAccessFile(file, "r");

			raf.seek(fromPos);

			while (count < length && (size = raf.read(buffer, 0, (length - count >= BUFFER_SIZE ? BUFFER_SIZE : (int) (length - count)))) != -1) {
				out.write(buffer, 0, size);

				out.flush();

				count += size;
			}

			return count;
		} catch (FileNotFoundException e) {
			log.error("Can not found the file for read.", e);
		} catch (IOException e) {
			log.error("Read file error.", e);
		} finally {
			if (out != null)
				out.close();

			if (raf != null)
				raf.close();
		}

		return -1;
	}

	public long output(final HttpServletRequest request, final HttpServletResponse response, String relativePath) {
		if (request == null || response == null || StringUtils.isBlank(relativePath))
			return -1;

		File file = new File(this.ROOT_DIR + relativePath);

		// 文件不存在
		if (!file.isFile())
			return -1;

		try {
			if (this.isRangeDownload(request)) {
				// 起止文件偏移量
				long fromPos = this.fromPos(request);
				long endPos = this.endPos(request);

				if (fromPos < 0)
					fromPos = 0;

				if (endPos < 0)
					endPos = file.length() - 1;

				long length = endPos - fromPos;

				if (length < 0)
					return -1;

				response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
				response.setHeader(this.HEADER_CONTENT_RANGE, "bytes " + fromPos + "-" + endPos + "/" + file.length());
				response.setHeader("Content-Length", "" + length);

				this.output(response.getOutputStream(), file, fromPos, endPos);
			} else {
				response.setHeader("Content-Length", "" + file.length());

				return this.output(response.getOutputStream(), file);
			}
		} catch (IOException e) {
			log.error("Output file on http error.", e);
		}

		return -1;
	}

	/**
	 * 获取本地存储文件的相对路径
	 * 
	 * @param file
	 * @return
	 * @throws IOException
	 */
	public String relativelyPath(File file) throws IOException {
		if (file == null || !file.isFile())
			return null;

		String path = file.getCanonicalPath();

		if (StringUtils.isBlank(path))
			return null;

		path = path.replace(this.ROOT_DIR, "");

		return path;
	}

	/**
	 * 通过相对路径删除文件
	 * 
	 * @param relativelyPath
	 * @return
	 */
	public boolean removeByRelativelyPath(String relativelyPath) {
		if (StringUtils.isBlank(relativelyPath))
			return false;

		String path = this.ROOT_DIR + relativelyPath;

		File file = new File(path);

		if (!file.isFile())
			return false;

		if (file.delete()) {
			File folder = file.getParentFile();

			// 删除空的父文件夹
			if (folder.listFiles().length <= 0)
				folder.delete();
		}

		return true;
	}

	/**
	 * 创建文件夹
	 * 
	 * @param dir
	 * @return
	 */
	private boolean mkDirs(String dir) {
		if (StringUtils.isBlank(dir))
			return false;

		File folder = new File(dir);

		if (folder.isDirectory())
			return true;

		return folder.mkdirs();
	}

	/**
	 * 获取一个新的UUID
	 * 
	 * @return
	 */
	private String newId() {
		return UUID.randomUUID().toString().replace("-", "");
	}

	/**
	 * 创建新文件
	 * 
	 * @param dir
	 * @return
	 */
	private File newFile(String dir) {
		if (StringUtils.isBlank(dir))
			return null;

		if (!dir.endsWith(File.separator))
			dir += File.separator;

		return new File(dir + this.newId());
	}

	/**
	 * 文件偏移量是否有效
	 * 
	 * @param fileLength
	 * @param pos
	 * @return
	 */
	private boolean validPosition(long fileLength, long pos) {
		return pos >= 0 && pos < fileLength;
	}

	/**
	 * 是否断点下载
	 * 
	 * @param request
	 * @return
	 */
	private boolean isRangeDownload(HttpServletRequest request) {
		String range = request.getHeader(this.HEADER_RANGE);

		return !StringUtils.isBlank(range) && range.startsWith(this.PREF_RANGE_VAL);
	}

	/**
	 * 获取range，如果不存在，则创建一个：Range:bytes=0-
	 * 
	 * @param request
	 * @return
	 */
	private String getRangeAndCreateIfNotExists(HttpServletRequest request) {
		String range = request.getHeader(this.HEADER_RANGE);

		if (StringUtils.isBlank(range))
			range = "bytes=0-";

		return range;
	}

	/**
	 * 获取文件起始偏移量
	 * 
	 * @param request
	 * @return
	 */
	private long fromPos(HttpServletRequest request) {
		if (request == null)
			return -1;

		String range = this.getRangeAndCreateIfNotExists(request);
		range = range.replace(this.PREF_RANGE_VAL, "");

		String[] tmp = range.split("\\-");

		if (tmp.length > 0) {
			try {
				return Long.parseLong(tmp[0]);
			} catch (NumberFormatException e) {
				e.printStackTrace();
				return -1;
			}
		}

		return -1;
	}

	/**
	 * 获取文件结束偏移量
	 * 
	 * @param request
	 * @return
	 */
	private long endPos(HttpServletRequest request) {
		if (request == null)
			return -1;

		String range = this.getRangeAndCreateIfNotExists(request);
		range = range.replace(this.PREF_RANGE_VAL, "");

		String[] tmp = range.split("\\-");

		if (tmp.length > 1) {
			try {
				return Long.parseLong(tmp[1]);
			} catch (NumberFormatException e) {
				e.printStackTrace();
				return -1;
			}
		}

		return -1;
	}

}
